﻿
package extremefx.i18n {
	import extremefx.DateTime;
	/**
	 * @author Marcelo Volmaro
	 */
	
	internal final class CCGregorian {
		/**
		 * An integer defining the epoch of the Gregorian calendar as fixed day number.
		 * The epoch is January 3, 1 C.E. (Julian).
		 */
		internal static const EPOCH:int = 1;
		
		/**
		 * The method tells whether the year is a leap year.
		 */
		internal static function _isLeapYear(pYear:int):Boolean {
			if (CCMath.mod(pYear, 4) != 0) return false;
			
			switch (CCMath.mod(pYear, 400)) {
				case 100:
					return false;
					
				case 200:
					return false;
					
				case 300:
					return false;
			}
			
			return true;
		}
		
		/**
		 * The method returns the fixed day number of the given Gregorian date.
		 */
		internal static function _fixedFromDMY(pDay:int, pMonth:int, pYear:int):int {
			var k:int = EPOCH - 1;
			k += 365 * (pYear-1);
			k += CCMath.div(pYear - 1, 4);
			k -= CCMath.div(pYear - 1, 100);
			k += CCMath.div(pYear - 1, 400);
			k += CCMath.div(367 * pMonth - 362, 12);
			
			if (pMonth > 2) {
				k += _isLeapYear(pYear) ? -1 : -2;
			}
	
			k += pDay;
			return k;
		}
		
		/**
		 * The method computes the Gregorian year from a fixed day number.
		 */
		internal static function _yearFromFixed(pDate:int):int {
			var remainder:Array = [];
			var n_400:int	= CCMath.divMod(remainder, pDate - EPOCH, 146097);
			var n_100:int	= CCMath.divMod(remainder, int(remainder[0]), 36524);
			var n_4:int		= CCMath.divMod(remainder, int(remainder[0]), 1461);
			var n_1:int		= CCMath.div(int(remainder[0]), 365);
	
			var year:int = 400*n_400 + 100*n_100 + 4*n_4 + n_1;
			return (n_100 == 4 || n_1 == 4) ? year : year + 1;
		}
		
		/**
		 * The method computes the Gregorian year and month from a fixed day
		 */
		private static function _myFromFixed(pDate:int):Array {
			var year:int = _yearFromFixed(pDate);
	
			var priorDays:int = pDate - _fixedFromDMY(1, 1, year);
			var correction:int;
			
			if (pDate < _fixedFromDMY(1, 3, year)) {
				correction = 0;
				
			} else if (_isLeapYear(year)) {
				correction = 1;
				
			} else {
				correction = 2;
			}
	
			var month:int = CCMath.div(12 * (priorDays + correction) + 373, 367);
			return [year, month];
		}
		
		/**
		 * The method computes the Gregorian year, month, and day from a fixed day number.
		 */
		private static function _dmyFromFixed(pDate:int):Array {
			var a:Array = _myFromFixed(pDate);
			a.push(pDate - _fixedFromDMY(1, int(a[1]), int(a[0])) + 1);
			return a;
		}
		
		/**
		 * A method computing the Gregorian month from a fixed day number.
		 */
		private static function _monthFromFixed(pDate:int):int {
			return int(_myFromFixed(pDate)[1]);
		}
		
		/**
		 * A method computing the day of the month from a fixed day number.
		 */
		private static function _dayFromFixed(pDate:int):int {
			return int(_dmyFromFixed(pDate)[2]);
		}
		
		/**
		 * The method computes the difference between two Gregorian dates.
		 */
	//		private static function _dateDifference(pDayA:int, pMonthA:int, pYearA:int, pDayB:int, pMonthB:int, pYearB:int):int {
	//			return	_fixedFromDMY(pDayB, pMonthB, pYearB) - _fixedFromDMY(pDayA, pMonthA, pYearA);
	//		}
		
		/**
		 * The method computes the number of the day in the year from a Gregorian date.
		 */
	//		private static function _dayNumber(pDay:int, pMonth:int, pYear:int):int {
	//			return _dateDifference(31, 12, pYear-1, pDay, pMonth, pYear);
	//		}
		
		/**
		 * The method computes the days remaining in the given Gregorian year from a Gregorian date.
		 */
	//		private static function _daysRemaining(pDay:int, pMonth:int, pYear:int):int {
	//			return _dateDifference(pDay, pMonth, pYear, 31, 12, pYear);
	//		}
		
		/**
		 * Adds months to the given date.
		 */
		internal static function _addMonths(pTime:DateTime, pMonths:int):DateTime {
			var rd:int = CCFixed._fromDateTime(pTime);
	
			var a:Array = _dmyFromFixed(rd);
			var year:int = a[0];
			var month:int = a[1];
			var day:int = a[2];
			
			month += pMonths;
			var remainder:Array = [];
			year += CCMath.divMod(remainder, month, 12);
			month = int(remainder[0]);
			
			var maxday:int = _getDaysInMonth(year, month);
			
			if (day > maxday) day = maxday;
			
			rd = _fixedFromDMY(day, month, year);
			var ret:DateTime = CCFixed._toDateTime(rd);
			ret.add(pTime.timeOfDay);
			return ret;
		}
		
		/**
		 * Adds years to the given date.
		 */
		internal static function _addYears(pTime:DateTime, pYears:int):DateTime {
			var rd:int = CCFixed._fromDateTime(pTime);
			
			var a:Array = _dmyFromFixed(rd);
			var year:int = a[0];
			var month:int = a[1];
			var day:int = a[2];
			
			year += pYears;
			var maxday:int = _getDaysInMonth(year, month);
			
			if (day > maxday) day = maxday;
			
			rd = _fixedFromDMY(day, month, year);
			var ret:DateTime = CCFixed._toDateTime(rd); 
			ret.add(pTime.timeOfDay);
			return ret;
		}
		
		/**
		 * Gets the day of the month from <code>pTime</code>.
		 */
		internal static function _getDayOfMonth(pTime:DateTime):int {
			return _dayFromFixed(CCFixed._fromDateTime(pTime));
		}
		
		/**
		 * The method gives the number of the day in the year.
		 */
		internal static function _getDayOfYear(pTime:DateTime):int {
			var rd:int = CCFixed._fromDateTime(pTime);
			var year:int = _yearFromFixed(rd);
			var rd1_1:int = _fixedFromDMY(1, 1, year);
			return rd - rd1_1 + 1;
		}
		
		/**
		 * A method that gives the number of days of the specified month of the <code>pMonth</code>.
		 */
		internal static function _getDaysInMonth(pYear:int, pMonth:int):int {
			var rd1:int = _fixedFromDMY(1, pMonth, pYear);
			var rd2:int = _fixedFromDMY(1, pMonth+1, pYear);
			return rd2 - rd1;
		}
		
		/**
		 * The method gives the number of days in the specified year.
		 */
		internal static function _getDaysInYear(pYear:int):int {
			var rd1:int = _fixedFromDMY(1, 1, pYear);
			var rd2:int = _fixedFromDMY(1, 1, pYear+1);
			return rd2 - rd1;
		}
		
		/**
		 * The method gives the number of the month of the specified date.
		 */
		internal static function _getMonth(pTime:DateTime):int {
			return _monthFromFixed(CCFixed._fromDateTime(pTime));
		}
		
		/**
		 * The method gives the number of the year of the specified date.
		 */
		internal static function _getYear(pTime:DateTime):int {
			return _yearFromFixed(CCFixed._fromDateTime(pTime));
		}
		
		/**
		 * A virtual method that tells whether the given day is a leap day.
		 */
		internal static function _isLeapDay(pYear:int, pMonth:int, pDay:int):Boolean {
			return _isLeapYear(pYear) && pMonth == 2 && pDay == 29;
		}
		
		internal static function _toDateTime(pYear:int, pMonth:int, pDay:int, pHour:int, pMinute:int, pSecond:int, pMSecond:int, pEra:int = -1):DateTime {
			return CCFixed._toDateTime2(_fixedFromDMY(pDay, pMonth, pYear), pHour, pMinute, pSecond, pMSecond);
		}
	}
}
